//////////////////////////////////////////////////////////////////////////////
//
//  Created by Philip Mulcahy on 30/01/2012.
//  Copyright (c) 2012 Philip Mulcahy. All rights reserved.
//
//  This file is part of the note-recog library.
//
//  note-recog is free software: you can redistribute it and/or modify
//  it under the terms of version 3 of the GNU Lesser General Public License 
//  as published by the Free Software Foundation.
//
//  note-recog is distributed is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with note-recog.  If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////

#include "Slice.h"
#include "Harmonic.h"
#include <cmath>
#include <stdexcept>
#include <map>
#include <cassert>
#include "Log.h"

LOG_DEFINE(Slice);

using namespace std;

static bool isInteresting(float startSeconds)
{
    vector<float> interestList;
    interestList.push_back(1.207);
    interestList.push_back(2.136);
    interestList.push_back(2.415);
    interestList.push_back(3.158);
    interestList.push_back(3.344);  
    interestList.push_back(4.087);
    interestList.push_back(4.458);
    interestList.push_back(6.409);
    interestList.push_back(11.33);
    
    for( float candidate : interestList )
    {
        const float tolerance = candidate / 1000.;
        const float error = abs(startSeconds - candidate);
        if( error < tolerance )
        {
            return true;
        }
    }
    
    return false;
}

Slice::Slice(
             size_t startFrameNumber,
             double startSeconds,
             const Spectrum& spec,
             const std::vector<short>& timeDomain
) :
  _spec(spec),
  _binWidthHz(spec.binwidthhz()),
  _fftLength(spec.abs().size()),
  _binCount(_fftLength),
  _startFrameNumber(startFrameNumber),
  _startSeconds(startSeconds),
  _rmsLevel(0.f),
  _timeDomain(timeDomain)
{
    if( _binCount < 3 )
    {
        throw std::length_error("too short");
    }
    
    typedef multimap<float,shared_ptr<const Harmonic> > mmap;

    mmap maximaByPeak;
    
    // Move a three bin cursor along the fft looking for maxima
    // and incrementing _rmsLevel with the squared levels.
    {
        size_t iBin = 1;
        
        typedef vector<float> fvec;
        
        for( fft_iterator rightIt = spec.abs().begin() + 2;
            rightIt != spec.abs().end();
            ++rightIt)
            
        {   
            const float left  = *(rightIt - 2);
            const float mid   = *(rightIt - 1);
            const float right = *rightIt;
            
            if( left < mid )
            {
                if( mid > right )
                {
                    const shared_ptr<const Harmonic> h(
                                               new Harmonic( 
                                                            spec.abs().begin(),
                                                            spec.abs().end(),
                                                            _binWidthHz,
                                                            iBin
                                                            ) 
                                               );

                    const float binFreq = iBin * _binWidthHz;
                    if( binFreq > 1500.f )
                    {
                        break;
                    }
                    
                    const float peak = h->peak();
                    const bool empty = maximaByPeak.empty();
                    bool insert = false;
                    
                    if( empty )
                    {
                        insert = true;
                    }
                    else
                    {
                        const float maxPeak = maximaByPeak.rbegin()->first;
                        if( peak > 0.75f * maxPeak )
                        {
                            insert = true;
                        }
                    }
                    
                    if( insert )
                    {
                        maximaByPeak.insert( mmap::value_type(peak,h) );
                    }
                }
            }
            _rmsLevel += mid * mid;
            ++iBin;
        }
        _rmsLevel = sqrt(_rmsLevel/_binCount);
    }
    
    if( maximaByPeak.empty() )
    {
        throw underflow_error("no harmonics found");
    }

    const bool interesting = isInteresting( startSeconds );
    
    if( interesting )
    {
        size_t bin = 0;
//        fft_iterator re = spec.abs().begin();
//        fft_iterator im = re + 1;
        float freq = 0;
        for( fft_iterator it = spec.abs().begin();
            it != spec.abs().end() && freq < 1000.f;
            ++it
        )
        {
            freq = _binWidthHz * bin;
            LOG_TRACE( "SPECBIN " << _startSeconds << " " << freq << " " << *it );
            ++bin;
        }
        
        LOG_DEBUG( "before pruning, harmonic count = " << maximaByPeak.size() );
#ifdef LOG_LEVEL_TRACE
        for( mmap::value_type& mx : maximaByPeak )
        {
            LOG_TRACE( mx.first << " -> " << *(mx.second) );
        }
#endif //#ifdef LOG_LEVEL_TRACE
    }

    // Remove weak harmonics
    const float peakCutOff = 0.15f * maximaByPeak.rbegin()->first;    
    maximaByPeak.erase( maximaByPeak.begin(), maximaByPeak.lower_bound( peakCutOff ) );
    
    if( interesting )
    {
        LOG_DEBUG( "after pruning, harmonic count = " << maximaByPeak.size() );
        for( mmap::value_type mx : maximaByPeak )
        {
            LOG_TRACE( mx.first << " -> " << *(mx.second) );
        }
    }
    
    mmap maximaByFreq;
    for( mmap::value_type h : maximaByPeak )
    {
        const float freq = h.second->freq();
        maximaByFreq.insert( mmap::value_type(freq, h.second) );
    }
    
    _fundamental = maximaByFreq.begin()->second;
    _dominant = maximaByPeak.rbegin()->second;
}

bool Slice::isGood() const
{
    const float ff = _fundamental->freq();
    return 150.f < ff && ff < 900.f;
}

ostream& operator<<( std::ostream& os, const Slice& slice )
{
    os 
    << "Slice{" << endl
    << "  rmsLevel : " << slice.rmsLevel() << endl
    << "  domLevel : " << slice.dominant().peak() << endl
    << "  domFreq  : " << slice.dominant().freq() << endl
    << "  funLevel : " << slice.fundamental().peak() << endl
    << "  funFreq  : " << slice.fundamental().freq() << endl
    << "}";
    
    return os;
}
